<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.0.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/wang-cheng/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/wang-cheng/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/wang-cheng/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/wang-cheng/images/logo.svg" color="#222">

<link rel="stylesheet" href="/wang-cheng/css/main.css">


<link rel="stylesheet" href="/wang-cheng/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"cheng-wang123.gitee.io","root":"/wang-cheng/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="算法题手撕：报数，N个人围成一圈编号1-N，每报M个数，就淘汰第M个人，然后从M+1开始重新报M个数，问最后剩下的那个人是谁？ 剑指offer 整数反转 easy难度 Redis的基本数据类型有哪些？ 字符串  列表List  集合Set  哈希Hash  Zset   还有三种不常用数据类型：geospatial、hyperloglogs、Streams HashSet和HashMap之间有什么">
<meta property="og:type" content="article">
<meta property="og:title" content="面经二">
<meta property="og:url" content="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/index.html">
<meta property="og:site_name" content="诚の博客">
<meta property="og:description" content="算法题手撕：报数，N个人围成一圈编号1-N，每报M个数，就淘汰第M个人，然后从M+1开始重新报M个数，问最后剩下的那个人是谁？ 剑指offer 整数反转 easy难度 Redis的基本数据类型有哪些？ 字符串  列表List  集合Set  哈希Hash  Zset   还有三种不常用数据类型：geospatial、hyperloglogs、Streams HashSet和HashMap之间有什么">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/OSI.jpg">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E5%AF%B9%E8%B1%A1%E7%BB%93%E6%9E%84.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/markWord.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E6%89%A7%E8%A1%8C%E5%BC%95%E6%93%8E%E6%89%A7%E8%A1%8C.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E6%9C%AA%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E4%BD%BF%E7%94%A8%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E8%84%91%E8%A3%82.png">
<meta property="article:published_time" content="2022-05-19T23:55:55.000Z">
<meta property="article:modified_time" content="2022-05-23T06:23:09.823Z">
<meta property="article:author" content="王诚">
<meta property="article:tag" content="面经">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/OSI.jpg">

<link rel="canonical" href="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>面经二 | 诚の博客</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/wang-cheng/atom.xml" title="诚の博客" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/wang-cheng/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">诚の博客</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">记录学习中的点点滴滴</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/wang-cheng/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/wang-cheng/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/wang-cheng/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/wang-cheng/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          面经二
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-05-20 07:55:55" itemprop="dateCreated datePublished" datetime="2022-05-20T07:55:55+08:00">2022-05-20</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-05-23 14:23:09" itemprop="dateModified" datetime="2022-05-23T14:23:09+08:00">2022-05-23</time>
              </span>

          
            <span class="post-meta-item" title="阅读次数" id="busuanzi_container_page_pv" style="display: none;">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">阅读次数：</span>
              <span id="busuanzi_value_page_pv"></span>
            </span>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <h2 id="算法题"><a href="#算法题" class="headerlink" title="算法题"></a><strong>算法题</strong></h2><p>手撕：报数，N个人围成一圈编号1-N，每报M个数，就淘汰第M个人，然后从M+1开始重新报M个数，问最后剩下的那个人是谁？</p>
<p>剑指offer 整数反转 easy难度</p>
<h2 id="Redis的基本数据类型有哪些？"><a href="#Redis的基本数据类型有哪些？" class="headerlink" title="Redis的基本数据类型有哪些？"></a><strong>Redis的基本数据类型有哪些？</strong></h2><ol>
<li><p>字符串</p>
</li>
<li><p>列表List</p>
</li>
<li><p>集合Set</p>
</li>
<li><p>哈希Hash</p>
</li>
<li><p>Zset</p>
</li>
</ol>
<p>还有三种不常用数据类型：geospatial、hyperloglogs、Streams</p>
<h2 id="HashSet和HashMap之间有什么关系？"><a href="#HashSet和HashMap之间有什么关系？" class="headerlink" title="HashSet和HashMap之间有什么关系？"></a><strong>HashSet和HashMap之间有什么关系？</strong></h2><p>HashSet仅仅存储对象，HashMap存储键值对</p>
<p>Hashset内部是 由Hashmap构造的，只用到了Hashmap的key。</p>
<figure class="highlight processing"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HashSet</span>&lt;E&gt;</span><br><span class="line">    extends AbstractSet&lt;E&gt;</span><br><span class="line">    implements Set&lt;E&gt;, Cloneable, java.<span class="property">io</span>.<span class="property">Serializable</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> serialVersionUID = <span class="number">-5024744406713321676</span>L;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> <span class="built_in">HashMap</span>&lt;E,<span class="built_in">Object</span>&gt; <span class="built_in">map</span>;</span><br><span class="line"> </span><br><span class="line">    <span class="comment">// Dummy value to associate with an Object in the backing Map</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="built_in">Object</span> PRESENT = <span class="keyword">new </span><span class="class title_">Object</span>();</span><br><span class="line"> </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Constructs a new, empty set; the backing &lt;tt&gt;HashMap&lt;/tt&gt; instance has</span></span><br><span class="line"><span class="comment">     * default initial capacity (16) and load factor (0.75).</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">HashSet</span>() &#123;</span><br><span class="line">	<span class="built_in">map</span> = <span class="keyword">new </span><span class="class title_">HashMap</span>&lt;E,<span class="built_in">Object</span>&gt;();</span><br><span class="line">    &#125;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="线程有几种状态？在Java中创建线程有哪几种方式？"><a href="#线程有几种状态？在Java中创建线程有哪几种方式？" class="headerlink" title="线程有几种状态？在Java中创建线程有哪几种方式？"></a><strong>线程有几种状态？在Java中创建线程有哪几种方式？</strong></h2><p>线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。</p>
<ol>
<li><p>通过继承Thread类创建线程</p>
</li>
<li><p>通过实现Runnable接口创建线程</p>
</li>
<li><p>使用Callable接口创建线程</p>
<h2 id="HashMap与HashTable区别"><a href="#HashMap与HashTable区别" class="headerlink" title="HashMap与HashTable区别"></a><strong>HashMap与HashTable区别</strong></h2></li>
<li><p>HashMap⽅法没有synchronized修饰，线程⾮安全，HashTable线程安全</p>
</li>
<li><p>HashMap允许key和value为null，⽽HashTable不允许</p>
</li>
</ol>
<h2 id="final作用"><a href="#final作用" class="headerlink" title="final作用"></a><strong>final作用</strong></h2><ol>
<li><p>修饰类</p>
<ul>
<li>被final修饰的类不能被继承</li>
<li>被final修饰的类所有成员方法都将被隐式修饰为final方法</li>
</ul>
</li>
<li><p>修饰变量</p>
<ul>
<li>被final修饰的变量一旦获得初始值就不可改变，不能被重新赋值</li>
<li>修饰成员变量：必须设置初始值，不能被重新赋值</li>
<li>修饰局部变量：必须设置初始值，不能被重新赋值</li>
<li>修饰基本类型变量：只能赋值一次，不能被重新赋值</li>
<li>修饰引用类型变量：引用在初始化后将永远指向一个内存地址，不可修改，但是该对象里面的信息，可以修改</li>
</ul>
</li>
<li><p>修饰方法</p>
<ul>
<li>方法不能被重写</li>
<li>方法内敛: 当调用一个方法时, 系统需要进行保存现场信息, 建立栈帧, 恢复线程等操作, 这些操作都是相对比较耗时的. 如果使用final修饰一个了一个方法a, 在其他调用方法a的类进行编译时, 方法a的代码会直接嵌入到调用a的代码块中.</li>
</ul>
</li>
</ol>
<h2 id="线程的同步方式"><a href="#线程的同步方式" class="headerlink" title="线程的同步方式"></a><strong>线程的同步方式</strong></h2><ol>
<li><p>同步方法，使用 synchronized关键字，可以修饰普通方法、静态方法，以及语句块。</p>
</li>
<li><p>同步代码块，用synchronized关键字修饰语句块。被该关键字修饰的语句块会自动被加上内置锁，从而实现同步</p>
</li>
<li><p>使用特殊域变量(volatile)实现线程同步。</p>
</li>
<li><p>使用重入锁实现线程同步，在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。</p>
</li>
<li><p>使用局部变量实现线程同步，如果使用ThreadLocal管理变量，则每一个使用该变量的线程都获得该变量的副本，副本之间相互独立，这样每一个线程都可以随意修改自己的变量副本，而不会对其他线程产生影响。  </p>
</li>
</ol>
<h2 id="路由器在哪层、路由器的转发原理、路由转发表有什么内容"><a href="#路由器在哪层、路由器的转发原理、路由转发表有什么内容" class="headerlink" title="路由器在哪层、路由器的转发原理、路由转发表有什么内容"></a><strong>路由器在哪层、路由器的转发原理、路由转发表有什么内容</strong></h2><img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/OSI.jpg" class title="OSI">
<p>由图可知，路由器在网络层</p>
<h3 id="路由器转发原理"><a href="#路由器转发原理" class="headerlink" title="路由器转发原理"></a><strong>路由器转发原理</strong></h3><p>通过路由表转发数据。</p>
<p>当分组到达一台路由器时，该路由器索引其转发表并决定该分组被指向的链路接口</p>
<h3 id="路由转发表有什么内容"><a href="#路由转发表有什么内容" class="headerlink" title="路由转发表有什么内容"></a><strong>路由转发表有什么内容</strong></h3><p>目的网络号；掩码；出接口；下一跳地址；度量值；管理距离。</p>
<h2 id="进程与线程区别"><a href="#进程与线程区别" class="headerlink" title="进程与线程区别"></a><strong>进程与线程区别</strong></h2><ol>
<li><p>根本区别：进程是操作系统资源分配的基本单位，而线程是处理器任务调度和执行的基本单位</p>
</li>
<li><p>资源开销：每个进程都有独立的代码和数据空间（程序上下文），程序之间的切换会有较大的开销；线程可以看做轻量级的进程，同一类线程共享代码和数据空间，每个线程都有自己独立的运行栈和程序计数器（PC），线程之间切换的开销小。</p>
</li>
<li><p>包含关系：如果一个进程内有多个线程，则执行过程不是一条线的，而是多条线（线程）共同完成的；线程是进程的一部分，所以线程也被称为轻权进程或者轻量级进程。</p>
</li>
<li><p>内存分配：同一进程的线程共享本进程的地址空间和资源，而进程之间的地址空间和资源是相互独立的</p>
</li>
<li><p>影响关系：一个进程崩溃后，在保护模式下不会对其他进程产生影响，但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。</p>
</li>
<li><p>执行过程：每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行，必须依存在应用程序中，由应用程序提供多个线程执行控制，两者均可并发执行</p>
</li>
</ol>
<h2 id="怎么解决死锁"><a href="#怎么解决死锁" class="headerlink" title="怎么解决死锁"></a><strong>怎么解决死锁</strong></h2><p>死锁产生的四个必要条件：</p>
<ol>
<li><p>互斥使用，即当资源被一个线程使用(占有)时，别的线程不能使用</p>
</li>
<li><p>不可抢占，资源请求者不能强制从资源占有者手中夺取资源，资源只能由资源占有者主动释放。</p>
</li>
<li><p>请求和保持，即当资源请求者在请求其他的资源的同时保持对原有资源的占有。</p>
</li>
<li><p>循环等待，即存在一个等待队列：P1占有P2的资源，P2占有P3的资源，P3占有P1的资源。这样就形成了一个等待环路。</p>
</li>
</ol>
<p>当上述四个条件都成立的时候，便形成死锁。当然，死锁的情况下如果打破上述任何一个条件，便可让死锁消失。</p>
<p>解决死锁问题的方法是：一种是用synchronized，一种是用Lock显式锁实现。</p>
<h2 id="二叉树遍历-4种、morris遍历具体、层次遍历"><a href="#二叉树遍历-4种、morris遍历具体、层次遍历" class="headerlink" title="二叉树遍历 4种、morris遍历具体、层次遍历"></a><strong>二叉树遍历 4种、morris遍历具体、层次遍历</strong></h2><p>二叉树遍历方式：先序遍历、中序遍历、后序遍历、层序遍历</p>
<p><strong>morris遍历</strong></p>
<p>假设来到当前节点cur，开始时cur来到头节点位置</p>
<ol>
<li><p>如果cur没有左孩子，cur向右移动(cur = cur.right)</p>
</li>
<li><p>如果cur有左孩子，找到左子树最右的节点mostRight</p>
<ol>
<li>如果mostRight的右指针指向空，让其指向cur，然后cur向左移动(cur = cur.left)</li>
<li>如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right)</li>
</ol>
</li>
<li>cur为空时停止遍历<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">morris</span><span class="params">(Node head)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (head == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">Node</span> <span class="variable">cur</span> <span class="operator">=</span> head;</span><br><span class="line">    <span class="type">Node</span> <span class="variable">mostRight</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">while</span> (cur != <span class="literal">null</span>) &#123;</span><br><span class="line">        mostRight = cur.left;</span><br><span class="line">        <span class="keyword">if</span> (mostRight != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">while</span> (mostRight.right != <span class="literal">null</span> &amp;&amp; mostRight.right != cur) &#123;</span><br><span class="line">                mostRight = mostRight.right;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (mostRight.right == <span class="literal">null</span>) &#123;</span><br><span class="line">                mostRight.right = cur;</span><br><span class="line">                cur = cur.left;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                mostRight.right = <span class="literal">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        cur = cur.right;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="对象头、markword"><a href="#对象头、markword" class="headerlink" title="对象头、markword"></a><strong>对象头、markword</strong></h2><img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E5%AF%B9%E8%B1%A1%E7%BB%93%E6%9E%84.png" class title="对象结构">
<p>对象的几个部分的作用：</p>
<ol>
<li><p>对象头中的Mark Word（标记字）主要用来表示对象的线程锁状态，另外还可以用来配合GC、存放该对象的hashCode；</p>
</li>
<li><p>Klass Word是一个指向方法区中Class信息的指针，意味着该对象可随时知道自己是哪个Class的实例；</p>
</li>
<li><p>数组长度也是占用64位（8字节）的空间，这是可选的，只有当本对象是一个数组对象时才会有这个部分；</p>
</li>
<li><p>对象体是用于保存对象属性和值的主体部分，占用内存空间取决于对象的属性数量和类型；</p>
</li>
<li><p>对齐字是为了减少堆内存的碎片空间</p>
</li>
</ol>
<p>Mark Word表达的锁状态含义</p>
<img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/markWord.png" class title="markWord">
<h2 id="类加载机制、准备和初始化在干什么"><a href="#类加载机制、准备和初始化在干什么" class="headerlink" title="类加载机制、准备和初始化在干什么"></a><strong>类加载机制、准备和初始化在干什么</strong></h2><p>java虚拟机将编译后的class文件加载到内存中，进行校验、转换、解析和初始化，到最终的使用。这就是java类加载机制；而 JVM 虚拟机执行 class 字节码的过程可以分为七个阶段：<strong>加载、验证、准备、解析、初始化、使用、卸载</strong>。</p>
<ol>
<li><p>编写Java文件</p>
</li>
<li><p>编译器编译Java源文件生成.class文件</p>
</li>
<li><p>JVM的类装载器装载.class文件， 在堆中生成一个代表这个类的Class对象，作为方法区中这些数据的访问入口 ，并为变量分配相应的内存（五步：加载、验证、准备、解析、初始化）</p>
</li>
<li><p>执行引擎执行</p>
</li>
</ol>
<img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E6%89%A7%E8%A1%8C%E5%BC%95%E6%93%8E%E6%89%A7%E8%A1%8C.png" class title="执行引擎执行">
<ol>
<li><p><strong>加载</strong></p>
<ol>
<li>通过一个类的全限定名来获取其定义的二进制字节流</li>
<li>将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构</li>
<li>在堆中生成一个代表这个类的Class对象，作为方法区中这些数据的访问入口。</li>
</ol>
</li>
<li><p><strong>验证</strong>:当代码数据被加载到内存中后，虚拟机就会对代码数据进行校验，看看这份代码是不是真的按照JVM规范去写的</p>
</li>
<li><p><strong>准备</strong>:备阶段主要为类变量分配内存并设置初始值,这些内存都在方法区分配</p>
<ol>
<li>类变量（static）会分配内存，但是实例变量不会，实例变量主要随着对象的实例化一块分配到java堆中，</li>
<li>这里的初始值指的是数据类型默认值，而不是代码中被显示赋予的值，比如public static int value = 1; //在这里准备阶段过后的value值为0，而不是1。赋值为1的动作在初始化阶段。</li>
</ol>
</li>
<li><p><strong>解析</strong>：解析阶段主要是虚拟机将常量池中的符号引用转化为直接引用的过程</p>
<p> 符号引用：以一组符号来描述所引用的目标，可以是任何形式的字面量，只要是能无歧义的定位到目标就好，就好比在班级中，老师可以用张三来代表你，也可以用你的学号来代表你，但无论任何方式这些都只是一个代号（符号），这个代号指向你（符号引用）</p>
<p> 直接引用：直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关，不同的虚拟机直接引用一般不同。</p>
<p> 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。</p>
</li>
<li><p><strong>初始化</strong>：简单来说就是执行类构造器<clinit>()方法的过程。</clinit></p>
<p> 到了初始化阶段，用户定义的 Java 程序代码才真正开始执行。</p>
<p> 我们知道，在准备阶段已经为类变量赋过一次值。在初始化阶端，程序员可以根据自己的需求来赋值了。</p>
<ol>
<li><p>假如这个类还没有被加载和连接，则程序先加载并连接该类</p>
</li>
<li><p>假如该类的直接父类还没有被初始化，则先初始化其直接父类</p>
</li>
<li><p>假如类中有初始化语句，则系统依次执行这些初始化语句</p>
<p>类初始化时机：只有当对类的主动使用的时候才会导致类的初始化</p>
<p>类的主动使用包括以下六种：</p>
</li>
<li><p>创建类的实例，也就是new的方式访问某个类或接口的静态变量</p>
</li>
<li><p>对该静态变量赋值调用类的静态方法</p>
</li>
<li><p>反射（如 Class.forName(“com.shengsiyuan.Test”)）</p>
</li>
<li><p>初始化某个类的子类，则其父类也会被初始化</p>
</li>
<li><p>Java虚拟机启动时被标明为启动类的类（JavaTest）</p>
</li>
<li><p>直接使用 java.exe命令来运行某个主类</p>
</li>
</ol>
</li>
</ol>
<h2 id="集合类的顶级接口"><a href="#集合类的顶级接口" class="headerlink" title="集合类的顶级接口"></a><strong>集合类的顶级接口</strong></h2><p>Collection接口</p>
<h2 id="synchronized的改进"><a href="#synchronized的改进" class="headerlink" title="synchronized的改进"></a><strong>synchronized的改进</strong></h2><ol>
<li><p><strong>锁膨胀</strong>：所谓的锁膨胀是指 synchronized 从无锁升级到偏向锁，再到轻量级锁，最后到重量级锁的过程，它叫做锁膨胀也叫做锁升级。</p>
</li>
<li><p><strong>锁消除</strong>：锁消除指的是在某些情况下，JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性，就会将这段代码所属的同步锁消除掉，从而到底提高程序性能的目的。</p>
<p> 锁消除的依据是逃逸分析的数据支持，如 StringBuffer 的 append() 方法，或 Vector 的 add() 方法，在很多情况下是可以进行锁消除的,比如以下这段代码：</p>
 <figure class="highlight oxygene"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="keyword">method</span><span class="params">()</span> <span class="comment">&#123;</span></span><br><span class="line"><span class="comment">    StringBuffer sb = new StringBuffer();</span></span><br><span class="line"><span class="comment">    for (int i = 0; i &lt; 10; i++) &#123;</span></span><br><span class="line"><span class="comment">        sb.append(&quot;i:&quot; + i);</span></span><br><span class="line"><span class="comment">    &#125;</span></span><br><span class="line">    <span class="title function_">return</span> <span class="title function_">sb</span>.<span class="title function_">toString</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p> 原因是 StringBuffer 的变量属于一个局部变量，并且不会从该方法中逃逸出去，所以此时我们就可以使用锁消除（不加锁）来加速程序的运行。</p>
</li>
<li><p><strong>锁粗化</strong>：锁粗化是指，将多个连续的加锁、解锁操作连接在一起，扩展成一个范围更大的锁。<br> 例如：</p>
 <figure class="highlight oxygene"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="keyword">method</span><span class="params">()</span> <span class="comment">&#123;</span></span><br><span class="line"><span class="comment">    StringBuffer sb = new StringBuffer();</span></span><br><span class="line"><span class="comment">    for (int i = 0; i &lt; 10; i++) &#123;</span></span><br><span class="line"><span class="comment">        sb.append(&quot;i:&quot; + i);</span></span><br><span class="line"><span class="comment">    &#125;</span></span><br><span class="line">    <span class="title function_">return</span> <span class="title function_">sb</span>.<span class="title function_">toString</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p> 如果在 for 循环中定义锁，那么锁的范围很小，但每次 for 循环都需要进行加锁和释放锁的操作，性能是很低的；但如果我们直接在 for 循环的外层加一把锁，那么对于同一个对象操作这段代码的性能就会提高很多</p>
<ul>
<li>锁粗化的作用：如果检测到同一个对象执行了连续的加锁和解锁的操作，则会将这一系列操作合并成一个更大的锁，从而提升程序的执行效率。</li>
</ul>
</li>
<li><p><strong>自适应自旋锁</strong>:自适应自旋锁是指，线程自旋的次数不再是固定的值，而是一个动态改变的值，这个值会根据前一次自旋获取锁的状态来决定此次自旋的次数,简单来说，如果线程自旋成功了，则下次自旋的次数会增多，如果失败，下次自旋的次数会减少。</p>
<ul>
<li>自旋锁是指通过自身循环，尝试获取锁的一种方式</li>
<li>自旋锁优点在于它避免一些线程的挂起和恢复操作，因为挂起线程和恢复线程都需要从用户态转入内核态，这个过程是比较慢的，所以通过自旋的方式可以一定程度上避免线程挂起和恢复所造成的性能开销。</li>
<li>自适应自旋锁避免了资源的浪费，让sychronized效率更高了。</li>
</ul>
</li>
</ol>
<h2 id="wait-notify"><a href="#wait-notify" class="headerlink" title="wait-notify"></a><strong>wait-notify</strong></h2><p><strong>wait</strong>:wait()方法是Object 类的方法，它的作用是使当前执行wait()方法的线程等待，在wait()所在的代码行处暂停执行，并释放锁，直到接到通知或中断。</p>
<p><strong>notify</strong>:notify()方法用来通知那些可能等待该锁的其他线程，如果有多个线程等待，则按照执行wait方法的顺序发出一次性通知（一次只能通知一个！），使得等待排在第一顺序的线程获得锁。需要说明的是，执行notify方法后，当前线程并不会立即释放锁，要等到程序执行完，即退出synchronized同步区域后。</p>
<p><strong>总结</strong>：wait 方法使线程暂停运行，而notify 方法通知暂停的线程继续运行。</p>
<h2 id="MySQL"><a href="#MySQL" class="headerlink" title="MySQL"></a><strong>MySQL</strong></h2><h3 id="索引下推是什么？"><a href="#索引下推是什么？" class="headerlink" title="索引下推是什么？"></a><strong>索引下推是什么？</strong></h3><p>什么是回表操作?</p>
<p>当我们在非主键索引上查找一行数据的时候，此时的查找方式是先搜索非主键索引树，拿到对应的主键值，再到主键索引树上查找对应的行数据。这种操作就叫作回表操作。</p>
<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">在这里有张用户表 <span class="keyword">user</span>，记录着用户的姓名，性别，身高，年龄等信息。表中 id 是自增主键，(<span class="type">name</span>,sex) 是联合索引。在这里用 <span class="number">1</span> 表示男，<span class="number">2</span> 表示女。现在需要查找所有姓王的男性信息。</span><br><span class="line"><span class="keyword">select</span> * <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> <span class="type">name</span> <span class="keyword">like</span> <span class="string">&#x27;王%&#x27;</span> <span class="keyword">and</span> sex = <span class="number">1</span>;</span><br></pre></td></tr></table></figure>
<img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E6%9C%AA%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8.png" class title="未索引下推">
<p>在未使用索引下推时，根据联合索引最左前缀原则，我们在非主键索引树上找到第一个满足条件的值时，通过叶子节点记录的主键值再回到主键索引树上查找到对应的行数据，再对比是否为当前所要查找的性别。</p>
<p>这样增加了回表的次数</p>
<p>当使用了索引下推后：</p>
<img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E4%BD%BF%E7%94%A8%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8.png" class title="使用索引下推">
<p>所以本质来说，索引下推就是只有符合条件再进行回表，对索引中包含的字段先进行判断，不符合条件的跳过。减少了不必要的回表操作。</p>
<p><strong>总结</strong>：索引下推主要是减少了不必要的回表操作。对于查找出来的数据，先过滤掉不符合条件的，其余的再去主键索引树上查找。</p>
<h3 id="覆盖索引是什么？"><a href="#覆盖索引是什么？" class="headerlink" title="覆盖索引是什么？"></a><strong>覆盖索引是什么？</strong></h3><p>MySQL 执行计划 explain 结果里的 key 有使用索引，如果 select 后面查询的字段都可以从这个索引的树中获取，这种情况一般可以说是用到了覆盖索引，extra 里一般都有 using index；覆盖索引一般针对的是辅助索引，整个査询结果只通过辅助索引就能拿到结果，不需要通过辅助索引树找到主键，再通过主键去主键索引树里获取其它字段值。</p>
<p>简单说就是只需要查询辅助索引，就可以获取到想要查询的值，并不需要再回表去主键索引处查询</p>
<h2 id="yong-GC-的发生时间？发生频繁，发生时间长的原因和解决方案？"><a href="#yong-GC-的发生时间？发生频繁，发生时间长的原因和解决方案？" class="headerlink" title="yong GC 的发生时间？发生频繁，发生时间长的原因和解决方案？"></a><strong>yong GC 的发生时间？发生频繁，发生时间长的原因和解决方案？</strong></h2><p>eden区满了会触发young GC</p>
<p><strong>发生频繁，发生时间长的原因以及解决方案</strong></p>
<ol>
<li>JVM参数设置不当：调整JVM参数</li>
</ol>
<p>2.代码不合理：更改代码</p>
<h2 id="CMS怎么标记垃圾的？三色标记法？G1的区别？"><a href="#CMS怎么标记垃圾的？三色标记法？G1的区别？" class="headerlink" title="CMS怎么标记垃圾的？三色标记法？G1的区别？"></a><strong>CMS怎么标记垃圾的？三色标记法？G1的区别？</strong></h2><p>CMS是使用三色标记法来标记垃圾的</p>
<ol>
<li><p>刚开始，所有的对象都是白色，没有被访问。</p>
</li>
<li><p>将GC Roots直接关联的对象置为灰色。</p>
</li>
<li><p>遍历灰色对象的所有引用，遍历后灰色对象本身置为黑色，引用置为灰色。</p>
</li>
<li><p>重复步骤3，直到没有灰色对象为止。</p>
</li>
<li><p>结束时，黑色对象存活，白色对象回收。</p>
</li>
</ol>
<h2 id="为什么GC要STW-stop-the-world-？"><a href="#为什么GC要STW-stop-the-world-？" class="headerlink" title="为什么GC要STW(stop the world)？"></a><strong>为什么GC要STW(stop the world)？</strong></h2><p>首先，如果不暂停用户线程，就意味着期间会不断有垃圾产生，永远也清理不干净。</p>
<p>其次，用户线程的运行必然会导致对象的引用关系发生改变，这就会导致两种情况：漏标和错标。</p>
<p>这也是三色标记算法存在的问题</p>
<h3 id="漏标是什么？"><a href="#漏标是什么？" class="headerlink" title="漏标是什么？"></a><strong>漏标是什么？</strong></h3><p>某个状态下，黑色-&gt;灰色-&gt;白色</p>
<p>如果一切顺利，不发生任何引用变化，GC线程顺着灰色的引用向下扫描,最后都变成黑色，都是存活对象</p>
<p>但是如果出现了这样一个状况，在扫描到灰色的时候，还没有扫描到这个白色对象，此时，黑色对象引用了这个白色对象，而灰色对象指向了别的对象，或者干脆指向了null，也就是取消了对白色对象的引用</p>
<p>那么我们会发现一个问题，根据三色标记规则，GC会认为,黑色对象是本身已经被扫描过，并且它所有指向的引用都已经被扫描过，所以不会再去扫描它有哪些引用指向了哪些对象，然后,灰色对象因为取消了对白色对象的引用，所以后面GC开始扫描所有灰色对象的引用时候，也不会再扫描到白色对象。</p>
<p><strong>最后结果就是,白色对象直到本次标记扫描结束,也是白色,根据三色标记规则,认为它是垃圾,被清理掉</strong></p>
<h3 id="错标是什么？"><a href="#错标是什么？" class="headerlink" title="错标是什么？"></a><strong>错标是什么？</strong></h3><p>假设GC线程已经遍历到B，此时用户线程执行了一下以下两个操作</p>
<figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">//B到D的引用被切断</span><br><span class="line">B.D<span class="operator">=</span>null<span class="comment">;	</span></span><br><span class="line">//A到D的引用被建立</span><br><span class="line">A.xx<span class="operator">=</span>D<span class="comment">;	</span></span><br></pre></td></tr></table></figure>
<p>B到D的引用被切断，且A到D的引用被建立。</p>
<p>此时GC线程继续工作，由于B不再引用D了，尽管A又引用了D，但是因为A已经标记为黑色，GC不会再遍历A了，所以D会被标记为白色，最后被当做垃圾回收。</p>
<p>可以看到错标的结果比漏表严重的多，浮动垃圾可以下次GC清理，而把不该回收的对象回收掉，将会造成程序运行错误。</p>
<h2 id="手写多线程生产者消费者（阻塞队列实现、信号量实现）"><a href="#手写多线程生产者消费者（阻塞队列实现、信号量实现）" class="headerlink" title="手写多线程生产者消费者（阻塞队列实现、信号量实现）"></a><strong>手写多线程生产者消费者（阻塞队列实现、信号量实现）</strong></h2><h2 id="java面向对象特点"><a href="#java面向对象特点" class="headerlink" title="java面向对象特点"></a><strong>java面向对象特点</strong></h2><p>抽象、封装、继承、多态</p>
<h2 id="HashMap啥时候扩容，为什么扩容？"><a href="#HashMap啥时候扩容，为什么扩容？" class="headerlink" title="HashMap啥时候扩容，为什么扩容？"></a><strong>HashMap啥时候扩容，为什么扩容？</strong></h2><p>当hashmap中的元素个数size超过数组长度*loadFactor时，就会进行数组扩容,loadFactor的默认值为0.75</p>
<p>为什么扩容：因为要保持HashMap的理想情况下的时间复杂度O(1)</p>
<h2 id="Reentrantlock加锁的源码"><a href="#Reentrantlock加锁的源码" class="headerlink" title="Reentrantlock加锁的源码"></a><strong>Reentrantlock加锁的源码</strong></h2><h3 id="非公平锁"><a href="#非公平锁" class="headerlink" title="非公平锁"></a><strong>非公平锁</strong></h3><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">final void <span class="built_in">lock</span>() &#123;</span><br><span class="line">    <span class="comment">//直接CAS加锁看能否成功</span></span><br><span class="line">    if (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line">        <span class="comment">//成功后设置当前线程</span></span><br><span class="line">        <span class="built_in">setExclusiveOwnerThread</span>(Thread.currentThread());</span><br><span class="line">    else</span><br><span class="line">        <span class="built_in">acquire</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面直接先尝试CAS,如果成功后把占用锁的线程设置成自己,加锁失败则进入acquire方法</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">public final void acquire(<span class="built_in">int</span> arg) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">try</span><span class="constructor">Acquire(<span class="params">arg</span>)</span><span class="operator"> &amp;&amp;</span></span><br><span class="line"><span class="operator">        </span>acquire<span class="constructor">Queued(<span class="params">addWaiter</span>(Node.EXCLUSIVE)</span>, arg))</span><br><span class="line">        <span class="comment">//补偿中断状态</span></span><br><span class="line">        self<span class="constructor">Interrupt()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>首先来看看tryAcquire方法里面做了什么,点进去会发现最终的实现是nonfairTryAcquire方法</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">final boolean nonfair<span class="constructor">TryAcquire(<span class="params">int</span> <span class="params">acquires</span>)</span> &#123;</span><br><span class="line">    final Thread current = <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>;</span><br><span class="line">    <span class="comment">//获取锁状态</span></span><br><span class="line">    <span class="built_in">int</span> c = get<span class="constructor">State()</span>;</span><br><span class="line">    <span class="keyword">if</span> (c<span class="operator"> == </span><span class="number">0</span>) &#123;<span class="comment">//直接尝试加锁,这里和外层的尝试加锁是一样的,只是再尝试一次</span></span><br><span class="line">        <span class="keyword">if</span> (compare<span class="constructor">AndSetState(0, <span class="params">acquires</span>)</span>) &#123;</span><br><span class="line">            set<span class="constructor">ExclusiveOwnerThread(<span class="params">current</span>)</span>;</span><br><span class="line">            <span class="comment">//加锁成功的话就直接返回true了,外层的acquire方法其实就直接结束了</span></span><br><span class="line">            return <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//判断是否是当前线程持有锁</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (current<span class="operator"> == </span>get<span class="constructor">ExclusiveOwnerThread()</span>) &#123;</span><br><span class="line">        <span class="comment">//此处可以说明是可重入锁</span></span><br><span class="line">        <span class="built_in">int</span> nextc = c + acquires;</span><br><span class="line">        <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>) <span class="comment">// overflow</span></span><br><span class="line">            throw <span class="keyword">new</span> <span class="constructor">Error(<span class="string">&quot;Maximum lock count exceeded&quot;</span>)</span>;</span><br><span class="line">        set<span class="constructor">State(<span class="params">nextc</span>)</span>;</span><br><span class="line">        return <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//返回false说明需要放入队列</span></span><br><span class="line">    return <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以发现tryAcquire做了两件事: 1,再次尝试加锁 2,持有锁的是否是自己 如果return true的话,方法就直接结束了,如果reture false的话,则会进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg)</p>
<p>那我们先来看下addWaiter方法<br><figure class="highlight crmsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">private <span class="keyword">Node</span> <span class="title">addWaiter</span>(<span class="keyword">Node</span> <span class="title">mode</span>) &#123;</span><br><span class="line">    //此时mode为null</span><br><span class="line">    <span class="keyword">Node</span> <span class="title">node</span> = new <span class="keyword">Node</span><span class="title">(Thread</span>.currentThread(), mode);</span><br><span class="line">    // Try the fast path of enq; backup to full enq on failure</span><br><span class="line">    //tail是结尾的指针,赋值给pred,当第一个线程进来的时候,tail为null</span><br><span class="line">    <span class="keyword">Node</span> <span class="title">pred</span> = tail;</span><br><span class="line">    //此处判断pred是否为null就是判断tail是否为null,也就是判断队尾是否有节点</span><br><span class="line"></span><br><span class="line">    //如果不为空,直接把当前节点放到tail的后面</span><br><span class="line">    if (pred != null) &#123;</span><br><span class="line">        //把当前节点放在tail后面</span><br><span class="line">        node.prev = pred;</span><br><span class="line">        //把当前节点设置为tail指针,其实就是把当前节点设置成最后一个</span><br><span class="line">        if (compareAndSetTail(pred, <span class="keyword">node</span><span class="title">)) &#123;//CAS</span>确保入队时是原子操作</span><br><span class="line">            //当前<span class="keyword">node</span><span class="title">成为新的队尾</span></span><br><span class="line"><span class="title">            pred</span>.next = <span class="keyword">node</span><span class="title">;</span></span><br><span class="line"><span class="title">            return</span> <span class="keyword">node</span><span class="title">;</span></span><br><span class="line"><span class="title">        &#125;</span></span><br><span class="line"><span class="title">    &#125;</span></span><br><span class="line"><span class="title">    //如果为空,执行下面的方法</span></span><br><span class="line"><span class="title">    enq</span>(<span class="keyword">node</span><span class="title">);</span></span><br><span class="line"><span class="title">    return</span> <span class="keyword">node</span><span class="title">;</span></span><br><span class="line"><span class="title">&#125;</span></span><br><span class="line"><span class="title">private</span> <span class="keyword">Node</span> <span class="title">enq</span>(final <span class="keyword">Node</span> <span class="title">node</span>) &#123;</span><br><span class="line">    //死循环初始化队列</span><br><span class="line">    for (;;) &#123;</span><br><span class="line">        <span class="keyword">Node</span> <span class="title">t</span> = tail;</span><br><span class="line">        if (t == null) &#123; // Must initialize</span><br><span class="line">            //死循环的第一次会初始化tail和head,刚开始的时候其实tail和head是一样的</span><br><span class="line">            if (compareAndSetHead(new <span class="keyword">Node</span><span class="title">()))</span></span><br><span class="line"><span class="title">                tail</span> = head;</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            //这里的代码和addWaiter中的是一样的</span><br><span class="line">            node.prev = t;</span><br><span class="line">            //死循环的第二次会把当前<span class="keyword">node</span><span class="title">接到之前的tail</span>位置后面</span><br><span class="line">            if (compareAndSetTail(t, <span class="keyword">node</span><span class="title">)) &#123;</span></span><br><span class="line"><span class="title">                t</span>.next = <span class="keyword">node</span><span class="title">;</span></span><br><span class="line"><span class="title">                return</span> t;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>可以发现addWaiter方法的作用其实就是把当前node设置到tail节点的后面,如果tail节点为空的话则执行enq方法初始化head和tail节点,无论结果如何,addWaiter都会返回当前的node</p>
<p>然后下一步进入acquireQueued方法</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">final boolean acquire<span class="constructor">Queued(<span class="params">final</span> Node <span class="params">node</span>, <span class="params">int</span> <span class="params">arg</span>)</span> &#123;</span><br><span class="line">    <span class="comment">//标志1</span></span><br><span class="line">    boolean failed = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">//标志2</span></span><br><span class="line">        boolean interrupted = <span class="literal">false</span>;</span><br><span class="line">        <span class="comment">//死循环</span></span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="comment">//找到当前node的prev节点</span></span><br><span class="line">            final Node p = node.predecessor<span class="literal">()</span>;</span><br><span class="line">            <span class="comment">//我们可以把head当做当前持有锁的节点,如果前一个是head,则开始重新获取锁</span></span><br><span class="line">            <span class="keyword">if</span> (p<span class="operator"> == </span>head<span class="operator"> &amp;&amp; </span><span class="keyword">try</span><span class="constructor">Acquire(<span class="params">arg</span>)</span>) &#123;</span><br><span class="line">                <span class="comment">//如果此时获取锁成功,则把当前节点放到head位置</span></span><br><span class="line">                set<span class="constructor">Head(<span class="params">node</span>)</span>;</span><br><span class="line">                <span class="comment">//node代替了之前的head,所以把之前的head置为null</span></span><br><span class="line">                p.next = null; <span class="comment">// help GC</span></span><br><span class="line">                failed = <span class="literal">false</span>;</span><br><span class="line">                <span class="comment">//加锁成功</span></span><br><span class="line">                return interrupted;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//有两种情况会到这</span></span><br><span class="line">            <span class="comment">//1,前一个不是head,说明还有其他兄弟在排队</span></span><br><span class="line">            <span class="comment">//2,前一个是head, 但是head还没有释放锁,加锁失败了</span></span><br><span class="line">            <span class="keyword">if</span> (should<span class="constructor">ParkAfterFailedAcquire(<span class="params">p</span>, <span class="params">node</span>)</span><span class="operator"> &amp;&amp;</span></span><br><span class="line"><span class="operator">                </span>park<span class="constructor">AndCheckInterrupt()</span>)</span><br><span class="line">                interrupted = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; finally &#123;</span><br><span class="line">        <span class="keyword">if</span> (failed)</span><br><span class="line">            cancel<span class="constructor">Acquire(<span class="params">node</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>有两种情况会去执行shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法</p>
<p>1,前一个不是head,说明还有其他兄弟在排队</p>
<p>2,前一个是head, 但是head还没有释放锁,加锁失败了</p>
<p>通过方法名称可以知道shouldParkAfterFailedAcquire是检测当前线程是否有挂起的资格</p>
<p>parkAndCheckInterrupt则是说明在shouldParkAfterFailedAcquire返回true的情况下,挂起线程</p>
<p>首先来看下shouldParkAfterFailedAcquire方法<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> static boolean should<span class="constructor">ParkAfterFailedAcquire(Node <span class="params">pred</span>, Node <span class="params">node</span>)</span> &#123;</span><br><span class="line">    <span class="comment">//上一个节点的状态</span></span><br><span class="line">    <span class="built_in">int</span> ws = pred.waitStatus;</span><br><span class="line">    <span class="comment">//如果是SIGNAL,直接返回true</span></span><br><span class="line">    <span class="keyword">if</span> (ws<span class="operator"> == </span>Node.SIGNAL)</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">            * This node has already set status asking a release</span></span><br><span class="line"><span class="comment">            * to signal it, so it can safely park.</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">        return <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">if</span> (ws &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">            * Predecessor was cancelled. Skip over predecessors and</span></span><br><span class="line"><span class="comment">            * indicate retry.</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            <span class="comment">//如果是CANCELLED状态,循环往队列前面找,直到找到一个SIGNAL的节点,然后把当前节点放到SIGNAL节点的后面</span></span><br><span class="line">            node.prev = pred = pred.prev;</span><br><span class="line">        &#125; <span class="keyword">while</span> (pred.waitStatus &gt; <span class="number">0</span>);</span><br><span class="line">        pred.next = node;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">            * waitStatus must be 0 or PROPAGATE.  Indicate that we</span></span><br><span class="line"><span class="comment">            * need a signal, but don&#x27;t park yet.  Caller will need to</span></span><br><span class="line"><span class="comment">            * retry to make sure it cannot acquire before parking.</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">        <span class="comment">//把之前的节点设置为SIGNAL</span></span><br><span class="line">        compare<span class="constructor">AndSetWaitStatus(<span class="params">pred</span>, <span class="params">ws</span>, Node.SIGNAL)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    return <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>可以发现shouldParkAfterFailedAcquire方法的作用就是保证当前node节点的prev节点的状态必须是SIGNAL,当满足这个条件后才会去执行parkAndCheckInterrupt方法,否则就会进入下一次死循环直到保证</p>
<p>prev节点的状态是SIGNAL,因为只有当prev节点是SIGNAL状态时,后续才会去唤醒下一个节点,当前节点才敢把自己挂起</p>
<p>然后我们看下parkAndCheckInterrupt方法<br><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="function"><span class="keyword">boolean</span> <span class="title">parkAndCheckInterrupt</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">//阻塞当前线程</span></span><br><span class="line">    LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line">    <span class="comment">//这里返回true后会清除掉状态</span></span><br><span class="line">    <span class="function"><span class="keyword">return</span> Thread.<span class="title">interrupted</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>这里就是直接挂起自己,等待head节点把自己唤醒,到这里的话,非公平锁的加锁过程就结束了</p>
<p>对state变量进行CAS,失败的话则新建一个node对象,把自己放到head的后面,然后此时又拿不到锁,没啥事做,那就自闭吧,把线程阻塞起来,防止cpu空转.</p>
<h3 id="公平锁是怎么实现公平的呢"><a href="#公平锁是怎么实现公平的呢" class="headerlink" title="公平锁是怎么实现公平的呢?"></a><strong>公平锁是怎么实现公平的呢?</strong></h3><p>我们看看公平锁和非公平锁两者源码的区别</p>
<p><strong>区别1</strong><br><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">final void <span class="built_in">lock</span>() &#123;</span><br><span class="line">    <span class="built_in">acquire</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//非公平</span></span><br><span class="line"></span><br><span class="line">final void <span class="built_in">lock</span>() &#123;</span><br><span class="line">    <span class="comment">//直接CAS加锁看能否成功</span></span><br><span class="line">    if (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line">        <span class="comment">//成功后设置当前线程</span></span><br><span class="line">        <span class="built_in">setExclusiveOwnerThread</span>(Thread.currentThread());</span><br><span class="line">    else</span><br><span class="line">        <span class="built_in">acquire</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>上面我们对比了lock方法 如果是非公平锁,新进来一个线程会直接去尝试加锁,根本不会排队 公平锁则直接进入了acquire方法</p>
<p><strong>区别二</strong><br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">protected final boolean <span class="keyword">try</span><span class="constructor">Acquire(<span class="params">int</span> <span class="params">acquires</span>)</span> &#123;</span><br><span class="line">    final Thread current = <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>;</span><br><span class="line">    <span class="built_in">int</span> c = get<span class="constructor">State()</span>;</span><br><span class="line">    <span class="keyword">if</span> (c<span class="operator"> == </span><span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!has<span class="constructor">QueuedPredecessors()</span><span class="operator"> &amp;&amp;</span></span><br><span class="line"><span class="operator">            </span>compare<span class="constructor">AndSetState(0, <span class="params">acquires</span>)</span>) &#123;</span><br><span class="line">            set<span class="constructor">ExclusiveOwnerThread(<span class="params">current</span>)</span>;</span><br><span class="line">            return <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (current<span class="operator"> == </span>get<span class="constructor">ExclusiveOwnerThread()</span>) &#123;</span><br><span class="line">        <span class="built_in">int</span> nextc = c + acquires;</span><br><span class="line">        <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>)</span><br><span class="line">            throw <span class="keyword">new</span> <span class="constructor">Error(<span class="string">&quot;Maximum lock count exceeded&quot;</span>)</span>;</span><br><span class="line">        set<span class="constructor">State(<span class="params">nextc</span>)</span>;</span><br><span class="line">        return <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    return <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>上面是公平锁的tryAcquire方法,我们可以看到这个和非公平锁的区别只是多了一个hasQueuedPredecessors()判断方法,同样,我们先看看这个方法是干啥的<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">hasQueuedPredecessors</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// The correctness of this depends on head being initialized</span></span><br><span class="line">    <span class="comment">// before tail and on head.next being accurate if the current</span></span><br><span class="line">    <span class="comment">// thread is first in queue.</span></span><br><span class="line">    <span class="comment">//判断队列中是否有优先级更高的等待线程</span></span><br><span class="line">    <span class="type">Node</span> <span class="variable">t</span> <span class="operator">=</span> tail; <span class="comment">// Read fields in reverse initialization order</span></span><br><span class="line">    <span class="type">Node</span> <span class="variable">h</span> <span class="operator">=</span> head;</span><br><span class="line">    Node s;</span><br><span class="line">    <span class="comment">//这里如果返回false 才会去CAS抢锁,否则就去排队</span></span><br><span class="line">    <span class="keyword">return</span> h != t &amp;&amp;</span><br><span class="line">        ((s = h.next) == <span class="literal">null</span> || s.thread != Thread.currentThread());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>总结一下,实现公平锁就是通过hasQueuedPredecessors方法来判断是否有高优先级的线程,而不是像非公平锁一样直接去抢锁.</p>
<p><strong>总结一下加锁流程</strong><br><figure class="highlight xquery"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">线程尝试加锁,加锁失败后会进入AQS队列,队列第一次会初始化一<span class="built_in">个head</span>节点,此<span class="built_in">时head</span>节点的内部线程是null,然后第一个加入队列的线程节点thread1会接在初始化后<span class="built_in">的head</span>节点后面,然后thread1会park自己,也就是说,除<span class="built_in">了head</span>节点外,队列中其他的节点都会park自己,然后持有锁的线程释放锁后,会唤<span class="built_in">醒head</span>节点后面的第一个节点,此时也就是thread1会被唤醒,thread获取到锁后会把自己置<span class="built_in">为head</span>,并且把自身的thread置为null,因为拿到锁后会setExclusiveOwnerThread(current);所以没有必要再持有线程.</span><br><span class="line">除了第一次初始化<span class="built_in">的head</span>外,所有<span class="built_in">的head</span>节点都是已经拿到锁的节点</span><br></pre></td></tr></table></figure></p>
<h2 id="Spring事务的实现方式及底层原理"><a href="#Spring事务的实现方式及底层原理" class="headerlink" title="Spring事务的实现方式及底层原理"></a><strong>Spring事务的实现方式及底层原理</strong></h2><ol>
<li><p>spring事务的实现方式</p>
<ul>
<li>spring框架提供了两种事务实现方式：编程式事务、声明式事务</li>
<li>编程式事务：在代码中进行事务控制。优点：精度高。缺点：代码耦合度高</li>
<li>声明式事务：通过@Transactional注解实现事务控制</li>
</ul>
</li>
<li><p>spring事务的底层原理</p>
<ul>
<li>Spring的事务管理是通过AOP代理实现的，对被代理对象的每个方法进行拦截，在方法执行前启动事务，在方法执行完成后根据是否有异常及异常的类型进行提交或回滚。</li>
</ul>
</li>
</ol>
<h2 id="Redis的集群模式，选举和脑裂"><a href="#Redis的集群模式，选举和脑裂" class="headerlink" title="Redis的集群模式，选举和脑裂"></a><strong>Redis的集群模式，选举和脑裂</strong></h2><p>集群模式：主从模式、哨兵模式、Cluster模式</p>
<p>脑裂：指在主从集群中，同时有两个主节点，它们都能接收写请求</p>
<p>那么什么时候会出现这种情况呢？</p>
<p>就是如果当前主库突然出现暂时性 “失联”，而并不是真的发生了故障，此时监听的哨兵会自动启动主从切换机制。当这个原始的主库从假故障中恢复后，又开始处理请求，但是哨兵已经选出了新的主库，这样一来，旧的主库和新主库就会同时存在，这就是脑裂现象。</p>
<p><strong>脑裂有什么影响？</strong></p>
<p>脑裂最直接的影响，就是客户端不知道应该往哪个主节点写入数据，结果就是不同的客户端会往不同的主节点上写入数据。严重的话，脑裂会进一步导致数据丢失。也就是说，等到哨兵让原主库和新主库做全量同步后，原主库在切换期间保存的数据就丢失了。</p>
<p>详细点来说，当主从切换后，从库升级为新主库，原主库和新主库会重新进行数据的全量同步，在这个过程中会发生数据的丢失</p>
<img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E8%84%91%E8%A3%82.png" class title="脑裂">
<ul>
<li>从服务器 Slave 向主服务器 Master 发送数据同步命令；</li>
<li>Master 主服务器接收到同步命令后，保存快照生成 RDB 文件，同时使用缓冲区记录从现在开始执行的所有写命令；</li>
<li><strong>Master 主服务器将快照文件（RDB文件）发送给 Slave 从服务器，Slave 从服务器接收到后，清空旧数据，载入新数据；</strong></li>
<li>Master 主服务器快照发送完后开始向 Slave 从服务器发送缓冲区的写命令，Slave 从服务器接收命令并执行，完成复制初始化；</li>
<li>此后 Master 主服务器每次执行一个写命令都会同步发送给 Slave 从服务器，保持相互之间数据的一致性；</li>
</ul>
<p>上述步骤中的关键一步就是，原主库需要清空本地的数据后加载新主库发送的 RDB 文件，这样一来，原主库在主从切换期间保存的新写数据就丢失了。</p>
<h2 id="spring-特性"><a href="#spring-特性" class="headerlink" title="spring 特性"></a><strong>spring 特性</strong></h2><p>三大特性：AOP、IOC、DI</p>
<h2 id="jdbc连接数据库的流程-原始jdbc使用流程"><a href="#jdbc连接数据库的流程-原始jdbc使用流程" class="headerlink" title="jdbc连接数据库的流程/原始jdbc使用流程"></a><strong>jdbc连接数据库的流程/原始jdbc使用流程</strong></h2><p>1、加载jdbc驱动程序；</p>
<p>2、创建数据库的连接；</p>
<p>3、创建preparedStatement；</p>
<p>4、执行SQL语句；</p>
<p>5、遍历结果集；</p>
<p>6、处理异常，关闭JDBC对象资源。</p>
<h2 id="throw和throws的区别"><a href="#throw和throws的区别" class="headerlink" title="throw和throws的区别"></a><strong>throw和throws的区别</strong></h2><ol>
<li><p>位置不同。throws用在函数上，后边跟的是异常类，可以跟多个异常类。throw用在函数内，后面跟的是异常对象。</p>
</li>
<li><p>throws用来声明异常，让调用者只知道该功能可能出现的问题，可以给出预先得处理方式。throw抛出具体的问题对象，执行到throw。功能就已经结束了跳转到调用者，并将具体的问题对象抛给调用者，也就是说throw语句独立存在时，下面不要定义其他语句，因为执行不到。</p>
</li>
</ol>
<p>3.throws表示出现异常的一种可能性，并不一定会发生这些异常，throw则是抛出了异常，执行throw则一定抛出了某种异常对象。</p>
<h2 id="终止for循环的方式"><a href="#终止for循环的方式" class="headerlink" title="终止for循环的方式"></a><strong>终止for循环的方式</strong></h2><ol>
<li><p>continue</p>
</li>
<li><p>break</p>
</li>
<li><p>return</p>
</li>
</ol>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/wang-cheng/tags/%E9%9D%A2%E7%BB%8F/" rel="tag"># 面经</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/" rel="prev" title="面经一">
      <i class="fa fa-chevron-left"></i> 面经一
    </a></div>
      <div class="post-nav-item">
    <a href="/wang-cheng/2022/05/23/%E9%9D%A2%E7%BB%8F%E4%B8%89/" rel="next" title="面经三">
      面经三 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%AE%97%E6%B3%95%E9%A2%98"><span class="nav-number">1.</span> <span class="nav-text">算法题</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%9C%89%E5%93%AA%E4%BA%9B%EF%BC%9F"><span class="nav-number">2.</span> <span class="nav-text">Redis的基本数据类型有哪些？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#HashSet%E5%92%8CHashMap%E4%B9%8B%E9%97%B4%E6%9C%89%E4%BB%80%E4%B9%88%E5%85%B3%E7%B3%BB%EF%BC%9F"><span class="nav-number">3.</span> <span class="nav-text">HashSet和HashMap之间有什么关系？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BA%BF%E7%A8%8B%E6%9C%89%E5%87%A0%E7%A7%8D%E7%8A%B6%E6%80%81%EF%BC%9F%E5%9C%A8Java%E4%B8%AD%E5%88%9B%E5%BB%BA%E7%BA%BF%E7%A8%8B%E6%9C%89%E5%93%AA%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F%EF%BC%9F"><span class="nav-number">4.</span> <span class="nav-text">线程有几种状态？在Java中创建线程有哪几种方式？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#HashMap%E4%B8%8EHashTable%E5%8C%BA%E5%88%AB"><span class="nav-number">5.</span> <span class="nav-text">HashMap与HashTable区别</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#final%E4%BD%9C%E7%94%A8"><span class="nav-number">6.</span> <span class="nav-text">final作用</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%90%8C%E6%AD%A5%E6%96%B9%E5%BC%8F"><span class="nav-number">7.</span> <span class="nav-text">线程的同步方式</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%B7%AF%E7%94%B1%E5%99%A8%E5%9C%A8%E5%93%AA%E5%B1%82%E3%80%81%E8%B7%AF%E7%94%B1%E5%99%A8%E7%9A%84%E8%BD%AC%E5%8F%91%E5%8E%9F%E7%90%86%E3%80%81%E8%B7%AF%E7%94%B1%E8%BD%AC%E5%8F%91%E8%A1%A8%E6%9C%89%E4%BB%80%E4%B9%88%E5%86%85%E5%AE%B9"><span class="nav-number">8.</span> <span class="nav-text">路由器在哪层、路由器的转发原理、路由转发表有什么内容</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%B7%AF%E7%94%B1%E5%99%A8%E8%BD%AC%E5%8F%91%E5%8E%9F%E7%90%86"><span class="nav-number">8.1.</span> <span class="nav-text">路由器转发原理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%B7%AF%E7%94%B1%E8%BD%AC%E5%8F%91%E8%A1%A8%E6%9C%89%E4%BB%80%E4%B9%88%E5%86%85%E5%AE%B9"><span class="nav-number">8.2.</span> <span class="nav-text">路由转发表有什么内容</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%BF%9B%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B%E5%8C%BA%E5%88%AB"><span class="nav-number">9.</span> <span class="nav-text">进程与线程区别</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%80%8E%E4%B9%88%E8%A7%A3%E5%86%B3%E6%AD%BB%E9%94%81"><span class="nav-number">10.</span> <span class="nav-text">怎么解决死锁</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BA%8C%E5%8F%89%E6%A0%91%E9%81%8D%E5%8E%86-4%E7%A7%8D%E3%80%81morris%E9%81%8D%E5%8E%86%E5%85%B7%E4%BD%93%E3%80%81%E5%B1%82%E6%AC%A1%E9%81%8D%E5%8E%86"><span class="nav-number">11.</span> <span class="nav-text">二叉树遍历 4种、morris遍历具体、层次遍历</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%AF%B9%E8%B1%A1%E5%A4%B4%E3%80%81markword"><span class="nav-number">12.</span> <span class="nav-text">对象头、markword</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6%E3%80%81%E5%87%86%E5%A4%87%E5%92%8C%E5%88%9D%E5%A7%8B%E5%8C%96%E5%9C%A8%E5%B9%B2%E4%BB%80%E4%B9%88"><span class="nav-number">13.</span> <span class="nav-text">类加载机制、准备和初始化在干什么</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E9%9B%86%E5%90%88%E7%B1%BB%E7%9A%84%E9%A1%B6%E7%BA%A7%E6%8E%A5%E5%8F%A3"><span class="nav-number">14.</span> <span class="nav-text">集合类的顶级接口</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#synchronized%E7%9A%84%E6%94%B9%E8%BF%9B"><span class="nav-number">15.</span> <span class="nav-text">synchronized的改进</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#wait-notify"><span class="nav-number">16.</span> <span class="nav-text">wait-notify</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#MySQL"><span class="nav-number">17.</span> <span class="nav-text">MySQL</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F"><span class="nav-number">17.1.</span> <span class="nav-text">索引下推是什么？</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%A6%86%E7%9B%96%E7%B4%A2%E5%BC%95%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F"><span class="nav-number">17.2.</span> <span class="nav-text">覆盖索引是什么？</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#yong-GC-%E7%9A%84%E5%8F%91%E7%94%9F%E6%97%B6%E9%97%B4%EF%BC%9F%E5%8F%91%E7%94%9F%E9%A2%91%E7%B9%81%EF%BC%8C%E5%8F%91%E7%94%9F%E6%97%B6%E9%97%B4%E9%95%BF%E7%9A%84%E5%8E%9F%E5%9B%A0%E5%92%8C%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%EF%BC%9F"><span class="nav-number">18.</span> <span class="nav-text">yong GC 的发生时间？发生频繁，发生时间长的原因和解决方案？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#CMS%E6%80%8E%E4%B9%88%E6%A0%87%E8%AE%B0%E5%9E%83%E5%9C%BE%E7%9A%84%EF%BC%9F%E4%B8%89%E8%89%B2%E6%A0%87%E8%AE%B0%E6%B3%95%EF%BC%9FG1%E7%9A%84%E5%8C%BA%E5%88%AB%EF%BC%9F"><span class="nav-number">19.</span> <span class="nav-text">CMS怎么标记垃圾的？三色标记法？G1的区别？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%BA%E4%BB%80%E4%B9%88GC%E8%A6%81STW-stop-the-world-%EF%BC%9F"><span class="nav-number">20.</span> <span class="nav-text">为什么GC要STW(stop the world)？</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%BC%8F%E6%A0%87%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F"><span class="nav-number">20.1.</span> <span class="nav-text">漏标是什么？</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%94%99%E6%A0%87%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F"><span class="nav-number">20.2.</span> <span class="nav-text">错标是什么？</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%89%8B%E5%86%99%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85%EF%BC%88%E9%98%BB%E5%A1%9E%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E3%80%81%E4%BF%A1%E5%8F%B7%E9%87%8F%E5%AE%9E%E7%8E%B0%EF%BC%89"><span class="nav-number">21.</span> <span class="nav-text">手写多线程生产者消费者（阻塞队列实现、信号量实现）</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#java%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%89%B9%E7%82%B9"><span class="nav-number">22.</span> <span class="nav-text">java面向对象特点</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#HashMap%E5%95%A5%E6%97%B6%E5%80%99%E6%89%A9%E5%AE%B9%EF%BC%8C%E4%B8%BA%E4%BB%80%E4%B9%88%E6%89%A9%E5%AE%B9%EF%BC%9F"><span class="nav-number">23.</span> <span class="nav-text">HashMap啥时候扩容，为什么扩容？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Reentrantlock%E5%8A%A0%E9%94%81%E7%9A%84%E6%BA%90%E7%A0%81"><span class="nav-number">24.</span> <span class="nav-text">Reentrantlock加锁的源码</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%9D%9E%E5%85%AC%E5%B9%B3%E9%94%81"><span class="nav-number">24.1.</span> <span class="nav-text">非公平锁</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%85%AC%E5%B9%B3%E9%94%81%E6%98%AF%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0%E5%85%AC%E5%B9%B3%E7%9A%84%E5%91%A2"><span class="nav-number">24.2.</span> <span class="nav-text">公平锁是怎么实现公平的呢?</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Spring%E4%BA%8B%E5%8A%A1%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%E5%8F%8A%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86"><span class="nav-number">25.</span> <span class="nav-text">Spring事务的实现方式及底层原理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis%E7%9A%84%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F%EF%BC%8C%E9%80%89%E4%B8%BE%E5%92%8C%E8%84%91%E8%A3%82"><span class="nav-number">26.</span> <span class="nav-text">Redis的集群模式，选举和脑裂</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#spring-%E7%89%B9%E6%80%A7"><span class="nav-number">27.</span> <span class="nav-text">spring 特性</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#jdbc%E8%BF%9E%E6%8E%A5%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E6%B5%81%E7%A8%8B-%E5%8E%9F%E5%A7%8Bjdbc%E4%BD%BF%E7%94%A8%E6%B5%81%E7%A8%8B"><span class="nav-number">28.</span> <span class="nav-text">jdbc连接数据库的流程&#x2F;原始jdbc使用流程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#throw%E5%92%8Cthrows%E7%9A%84%E5%8C%BA%E5%88%AB"><span class="nav-number">29.</span> <span class="nav-text">throw和throws的区别</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BB%88%E6%AD%A2for%E5%BE%AA%E7%8E%AF%E7%9A%84%E6%96%B9%E5%BC%8F"><span class="nav-number">30.</span> <span class="nav-text">终止for循环的方式</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">王诚</p>
  <div class="site-description" itemprop="description">命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/wang-cheng/archives/">
        
          <span class="site-state-item-count">32</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/wang-cheng/tags/">
          
        <span class="site-state-item-count">12</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">王诚</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Gemini</a> 强力驱动
  </div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/wang-cheng/lib/anime.min.js"></script>
  <script src="/wang-cheng/lib/velocity/velocity.min.js"></script>
  <script src="/wang-cheng/lib/velocity/velocity.ui.min.js"></script>

<script src="/wang-cheng/js/utils.js"></script>

<script src="/wang-cheng/js/motion.js"></script>


<script src="/wang-cheng/js/schemes/pisces.js"></script>


<script src="/wang-cheng/js/next-boot.js"></script>




  















  

  

  

</body>
</html>
